'''
NurbsCad
Archivo escena utilizado principalmente por el archivo gui.py 
Autor: Gustavo Enrique Bellino
Contacto: gushbellino@gmail.com
Fecha: Junio de 2009
Descripcion: archivo con una clase llamada Escena que hereda una serie de elementos que permite renderizar objetos creados
con opengl. Ademas, administra estos elementos.
Licencia: GNU Public License version 2
'''

#Importa las librerias necesarias
from __future__ import division
import os, sys,  math, pygtk, time, gtk.glade, copy, gc
pygtk.require('2.0')
from gtk.gtkgl.apputils import *
#Se agrega al path la carpeta ./mod
modulesdir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'mod'))
sys.path.insert(0, modulesdir)
#Se incluyen los objetos
from camara import Camara
from lib.euclid import Vector3
from library import *

#Intenta cargar OpenGL, si no lo logra lanza un error y termina
try:
  from OpenGL.GLUT import *
  from OpenGL.GL import *
  from OpenGL.GLU import *
except:
  print 'ERROR: PyOpenGL is not installed properly.'
  sys.exit()

#los objetos que maneja la clase
path = os.path.join(os.getcwd(), 'mod/modules.py')
try:
    #modulos que maneja la escena
    execfile(path)
except:
    print 'Existe un problema en el archivo modules.py, corrobore su existencia, permisos y/o estructura.'
    sys.exit()


class Escena(Library, GLScene, GLSceneButton, GLSceneButtonMotion):
    ''' Clase que posibilita el renderizado de elementos en opengl gracias a que hereda de GLScene, GLSceneButton, GLSceneButtonMotion.
    Estas heredaciones necesitan que existan las siguientes funciones: init, display, reshape, button_press,
    A su vez administra estos elementos creados para ser renderizados.'''
    def __init__(self, gui):
        ''' 
        Inicializa la clase de renderizado.
        @param: gui, objeto Interfaz.
        '''
        #objeto Interfaz, con este elemento se tiene acceso a todas las funciones de la interfaz.
        self.gui = gui
        
        #Trae las matrices.
        glutInit(sys.argv)
        GLScene.__init__(self, gtk.gdkgl.MODE_RGB | gtk.gdkgl.MODE_DEPTH | gtk.gdkgl.MODE_DOUBLE)
        
        #obtiene las clases permitidas del archivo modules.py
        self.ALLOWED_CLASSES = ALLOWED_CLASSES
        
        #diccionario con los objetos que se van creando
        self.objects = {}
        self.selectedObject = None
        
        #moviendo la camara
        self.moving = False #SEE
        optimizar = False
        if(not optimizar):
            eyes = Vector3(8, 3, 8)
            lookAt = Vector3(0, 0, 0)
            up = Vector3(0, 1, 0)
        else:
#        optimizacion
            eyes = vector3P.Vector3b()
            eyes.set(8, 3, 8)
            lookAt = vector3P.Vector3b()
            lookAt.set(0, 0, 0)
            up = vector3P.Vector3b()
            up.set(0, 1, 0)
        
        #camara
        self.camara = Camara(eyes, lookAt, up, self)#inicializo la camara
        
        #plano
        indice = self.getNextIdentifier()
        omega = Plano(indice, self)
        self.objects[omega.identifier] = omega
        self.object = None
        
        #on mouse over
        self.selectOver = False
        
        #inicializa el GLScene
        self.ang = 1
        
        #dimensiones del canvas
        self.width = 678
        self.height = 558
        #anti alias
        self.antialias  = False
        self.objectToPaste = None

    def init(self):
        glEnable(GL_POINT_SMOOTH); 
        glEnable(GL_LINE_SMOOTH); 
        glEnable(GL_POLYGON_SMOOTH);
        glEnable(GL_DEPTH_TEST)
        glClearColor(1, 1, 1, 0)
        glClearDepth(1.0)
        #inicializa la camara
        self.camara.initialize()
        return

    def display(self, width, height):
        #self.setLigths() TODO
        self.camara.regen()
        glMatrixMode(GL_MODELVIEW)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        #self.setMaterial() TODO
        self.draw_objects()
        return

    def draw_objects(self): 
        glPushMatrix()
        self.drawObjects()
        glPopMatrix()
        return 

    def reshape(self, width, height):
        self.width = width - 2
        self.height = height - 2
        glViewport(0, 0, width, height)
        ratio = width/height
        self.camara.regen()
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluPerspective(40.0, ratio, 1.0, 150.0)
        self.camara.regen()
        #self.setLigths() TODO
        for indice in self.objects.keys():
            if(isinstance(self.objects[indice], NurbSurface)):
                self.objects[indice].Resize(width, height)
        self.queue_draw()

    def button_press (self, width, height, event):
        # On left mouse button click, append one brush stroke
        # that needs to be drawn.
        self.callbackMouse(event.button, event.x, event.y, width, height)
        self.gui.cargarBotones(self.selectedObject)
        self.gui.cargarAtributos(self.selectedObject)
        self.queue_draw()

    def button_release (self, width, height, event):
        self.checkRemoves()
        self.gui.cargarBotones(self.selectedObject)
        self.gui.cargarAtributos(self.selectedObject)
        objetos = self.getObject()
        self.gui.cargarTree(objetos)
        self.queue_draw()

    def button_motion (self, width, height, event):
        # Append subsequent strokes to the list due to drag motion.
        self.callbackMotion(event.x, event.y, width, height)
        self.queue_draw()

    def setLigths(self): #TODO
        if(self.camara.ligthsActive):
            #direccion de los poligonos
            glFrontFace(GL_CCW)
            glPolygonMode(GL_FRONT_AND_BACK,GL_FILL)
            glCullFace(GL_BACK)                                                 #se habilita por objeto (cerrado o abierto)
            lambient=[0.3, 0.3, 0.3, 1]                                      #luz ambiente
            ldiffuse= [0.7, 0.7, 0.7, 1]                                       #luz difusa
            #define luces
            glLightfv(GL_LIGHT0,GL_AMBIENT,lambient)
            glLightfv(GL_LIGHT0,GL_DIFFUSE,ldiffuse)
            lpos=[200,-200, 200,1]
            glLightfv(GL_LIGHT0,GL_POSITION,lpos)
            #caras de atras y adelante distintos (1) o iguales (0)
            glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,1)
            glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER,0)
            glEnable(GL_LIGHT0)
            glEnable(GL_LIGHTING)
        else:
            #grilla de lineas
            glDisable(GL_LIGHTING)
            glDisable(GL_NORMALIZE) 
            glDisable(GL_AUTO_NORMAL)

    def setMaterial(self): #TODO
        #material estandar
        fambient=[0.2,0.09,0.03,0.5]
        fdiffuse=[0.5,0.2,0.07,0.5]
        fspecular=[0.6,0.2,0.07,0.5]
        bambient=[0.1,0.2,0.2,0.5]
        bdiffuse=[0.5,0.9,0.6,.5]
        bspecular=[0.3,0.3,0.3,.5]
        fshininess=50
        bshininess=10
        glMaterialfv(GL_FRONT,GL_AMBIENT,fambient)
        glMaterialfv(GL_FRONT,GL_DIFFUSE,fdiffuse)
        glMaterialfv(GL_FRONT,GL_SPECULAR,fspecular)
        glMateriali(GL_FRONT,GL_SHININESS,fshininess)
        glMaterialfv(GL_BACK,GL_AMBIENT,bambient)
        glMaterialfv(GL_BACK,GL_DIFFUSE,bdiffuse)
        glMaterialfv(GL_BACK,GL_SPECULAR,bspecular)
        glMateriali(GL_BACK,GL_SHININESS,bshininess)

    def draw_ejes(self): #draw_it = True TODO
        tam = 5.0
        lineWidth = 2
        p0 = Vector3(0, 0, 0)
        glPushMatrix()
        
        #Eje X
        color = [1,0,0,0]
        self.draw_line(p0, Vector3(0.9*tam, 0, 0), color, lineWidth)
        posWin = self.convertTo2D(1*tam, 0, 0)
        self.drawText('X', int(posWin.x), int(self.height - posWin.y), self.height, self.width, 12) 
        
        #Eje Y
        color = [0,1,0,0]
        self.draw_line(p0, Vector3(0, 0.9*tam, 0), color, lineWidth)
        posWin = self.convertTo2D(0, 1*tam, 0)
        self.drawText('Y', int(posWin.x), int(self.height- posWin.y), self.height, self.width, 12) 
        
        #Eje Z
        color = [0,0,1,0]
        self.draw_line(p0, Vector3(0, 0,  0.9*tam), color, lineWidth)
        posWin = self.convertTo2D(0, 0, 1*tam)
        self.drawText('Z', int(posWin.x), int(self.height- posWin.y), self.height, self.width, 12)
        glPopMatrix()

    def addObject(self, object):
        #agrega en la lista con el identificador del elemento
        if(isinstance(object, self.ALLOWED_CLASSES)):
            self.objects[object.identifier] = object
            return True
        else:
            return False

    def checkRemoves(self):
        for object in self.objects.values():
            if(object.autoEliminate):
                indice = object.identifier
                self.objects.pop(int(indice))
                self.selectedObject = self.camara
        return

    def deleteAllObjects(self):
        for indice in self.objects.keys():
            if(not isinstance(self.objects[indice], Plano)):
                self.deleteObject(indice)

    def deleteObject(self, indice=None):
        ''' Elimina el objeto que esta seleccionado (accion por defecto) o el elemento con el indice que se le pasa por parametro'''
        if(indice==None):
            indice = self.selectedObject.identifier
        self.objects.pop(int(indice))

    def deleteObjectByType(self, strType):
        for indice in self.objects.keys():
            if(self.objects[indice].__class__.__name__ == strType):
                self.deleteObject(indice)

    def getNextIdentifier(self):
        ''''Si no es el ultimo indice posible (255) retorna el siguiente posible indice del diccionario'''
        if(self.objects.__len__()==0):
            return 1
        keys = self.objects.keys()
        keys.sort()
        indiceLast = keys[-1]
        if(indiceLast + 1 < 256):
            return indiceLast + 1
        else:
            return None #llego a la maxima cantidad de indices

    def selectNone(self):
        ''' Des-selecciona todos los objetos '''
        for obj in self.objects.values():
            obj.bSelected = False
        return

    def getPixelColor(self, x, y):
        ''' Retorna la cantidad del color rojo del pixel '''
        inc = 255
        return round(inc * glReadPixels(x, y , 1, 1, GL_RED, GL_FLOAT))

    def selectObject(self, x, y):
        ''' Retorna el objeto seleccionado o None en caso de no haber ninguno'''
        ''' Parametros: x , H - y de la pantalla '''
        #dibuja los objetos en el back buffer con su color correspondiente
#        if(self.camara.ligthsActive): TODO
#            glDisable(GL_LIGHTING)
#            glDisable(GL_LIGHT0)
        
        glMatrixMode(GL_MODELVIEW)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        
        #dibuja los objetos con los colores que los identifican
        self.drawObjects(selectMode = True)
        
        #obtiene el color que el mouse selecciono
        indice = self.getPixelColor(x, y)
        
#        if(self.camara.ligthsActive): TODO
#            glEnable(GL_LIGHTING)
#            glEnable(GL_LIGHT0)
        
        #limpia nuevamente el color buffer para que no queden dibujados
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        
        #deselecciona todos los objetos
        self.selectNone()
        
        #si un objecto esta seleccionado se lo setea
        if(indice in self.objects.keys()):
            self.objects[indice].bSelected = True
            return self.objects[indice]
        else:
            return self.camara
  
    #callbacks
    def callbackMouse(self, button,  x, y, width, height):
        ''' Representa la funcion que se debe ejecutar cuando se realiza un click en la GUI.
        x,y: coordenadas de la pantalla, camara: instancia de la camara para saber la recta eyes->lookAt'''
        self.selectedObject = self.selectObject(x, height - y)
        red = self.getPixelColor(x, y)
        color = (red, red, red)
        self.moving = True
        if(self.selectedObject != None):
            if(isinstance(self.selectedObject, Camara)):
                self.selectedObject.Mouse(button, x, y)
            elif(isinstance(self.selectedObject, Plano)):
                self.selectedObject.Mouse(x, y, self.camara, color)
            elif(isinstance(self.selectedObject, NurbSurface)):
                plano = self.getObject(1)
                self.selectedObject.Mouse(x, y, self.camara, plano)
#            elif(isinstance(self.selectedObject, Punto)):
#                plano = self.getObject(1)
#                self.selectedObject.Mouse(x, y, self.camara, plano)
#            elif(isinstance(self.selectedObject, Perspectiva)):
#                plano = self.getObject(1)
#                self.selectedObject.Mouse(x, y, self.camara, plano)
            else:
                plano = self.getObject(1)
                self.selectedObject.Mouse(x, y, self.camara, plano)
        return self.selectedObject
    
    def callbackMotion(self, x, y, width, height):
        ''' Representa la funcion que se debe ejecutar cuando se realiza un click y movimiento en la GUI.'''
        if(self.selectOver):
            self.selectObject(x, height - y)
        if(self.selectedObject != None):
            #los parametros varian segun el objeto
            if(isinstance(self.selectedObject, Camara)):
                plano = self.getObject(1)
                self.selectedObject.Motion(x, y, width, height,  plano)
            elif(isinstance(self.selectedObject, Plano)):
                self.selectedObject.Motion(x, y, self.camara, speed=1)
            elif(isinstance(self.selectedObject, NurbSurface)):
                plano = self.getObject(1)
                self.selectedObject.Motion(x, y, width, height, self.camara, plano)
#            elif(isinstance(self.selectedObject, Punto)):
#                plano = self.getObject(1)
#                self.selectedObject.Motion(x, y, width, height, self.camara, plano)
#            elif(isinstance(self.selectedObject, Perspectiva)):
#                plano = self.getObject(1)
#                self.selectedObject.Motion(x, y, width, height, self.camara, plano)
            else: #by default
                plano = self.getObject(1)
                self.selectedObject.Motion(x, y, width, height, self.camara, plano)

    def callbackScroll(self, direction, x, y):
        if(self.selectedObject != None):
            if(isinstance(self.selectedObject, Camara)):
                self.selectedObject.Scroll(direction, x, y)
            elif(isinstance(self.selectedObject, Plano)):
                self.selectedObject.Scroll(direction)
            elif(isinstance(self.selectedObject, NurbSurface)):
                plano = self.getObject(1)
                self.selectedObject.Scroll(plano, direction)
        self.queue_draw()

    def drawObjects(self, selectMode=False):
        ''' Dibuja todos los objetos. Paramatros: selectMode, False para el dibujado normal, True para el modo seleccion'''      
        for obj in self.objects.values():
#            obj.draw(selectMode, self.moving)
            obj.draw(selectMode)
        self.drawFloor()
        self.draw_ejes()

    def drawFloor(self, max = 30, color = (0, 0, 0)):
        glPushMatrix()
        glBegin(GL_POINTS)
        glColor3f(color[0], color[1], color[2])
        sep = 0.8
        for x in range(max):
            for z in range(max):
                glVertex3d(x * sep , 0, z * sep)
                glVertex3d(x * sep, 0, -z * sep)
                glVertex3d(-x * sep, 0, z * sep)
                glVertex3d(-x * sep, 0, -z * sep)
        glEnd()
        glPopMatrix()

    def getObject(self, indice = None):
        if(indice==None):
            return self.objects
        else:
            return self.objects[indice]

    def copy(self, objeto):
        if objeto.type == 'nurb':
            self.objectToPaste = objeto

    def paste(self):
        if self.objectToPaste!=None:
            indice = self.getNextIdentifier()
            cantU = self.objectToPaste.cantU 
            cantV = self.objectToPaste.cantV 
            ordenU = self.objectToPaste.ordenU
            ordenV = self.objectToPaste.ordenV
            ctrlPoints = copy.copy(self.objectToPaste.ctrlPoints)
#            ini = ctrlPoints[0]
#            for i in range(cantV*cantU):
#                ctrlPoints[i] = ctrlPoints[i] - ini
            nurb = NurbSurface(indice, self, [cantU, cantV, ordenU, ordenV])
            nurb.setCtrlPoints(ctrlPoints)
            self.addObject(nurb)
            self.selectedObject = self.getObject(indice)
            self.gui.cargarBotones(self.selectedObject)
            self.gui.cargarAtributos(self.selectedObject)
            objetos = self.getObject()
            self.gui.cargarTree(objetos)

    def set_info(self, text):
        self.gui.set_info(text)

    def set_all_nurbs_visibility(self, element, bool):
        for indice in self.objects.keys():
            if(isinstance(self.objects[indice], NurbSurface)):
                if element == 'curve':
                    self.objects[indice].visibleCurve = bool
                    if bool:
                        string = 'visibles'
                    else:
                        string = 'invisibles'
                    self.set_info('Todas las curvas '+string+'.')
                if element == 'poligon':
                    self.objects[indice].visiblePoligon = bool
                    if bool:
                        string = 'visibles'
                    else:
                        string = 'invisibles'
                    self.set_info('Todas los poligonos '+string+'.')
                if element == 'points':
                    self.objects[indice].visiblePoints = bool
                    if bool:
                        string = 'visibles'
                    else:
                        string = 'invisibles'
                    self.set_info('Todas los puntos '+string+'.')
        self.queue_draw()

    def proponerActivar(self, bool):
        for indice in self.objects.keys():
            if(isinstance(self.objects[indice], NurbSurface)):
                self.objects[indice].proposal = bool
